/*
 *  Copyright 2009 Johan Maasing
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package com.google.code.pj2r.resources;

import com.google.code.pj2r.Marshaller;
import com.google.code.pj2r.MarshallingException;
import com.google.code.pj2r.ResourceConflictException;
import com.google.code.pj2r.ResourceCreationException;
import com.google.code.pj2r.ResourceDispatcher;
import com.google.code.pj2r.ResourceException;
import com.google.code.pj2r.ResourceFactory;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 *
 * Implements the servlet methods.
 */
public class ResourceDispatcherDelegate {

	private final ResourceFactory resourceFactory;
	private final Marshaller marshaller;

	public ResourceDispatcherDelegate(final ResourceFactory resourceFactory, final Marshaller marshaller) {
		this.resourceFactory = resourceFactory;
		this.marshaller = marshaller;
	}

	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		if (isAcceptable(request)) {
			try {
				RequestProcessorResult result = processRequest(request);
				if (result.getParsedPath().getId() == null) {
					String json = result.getResource().list();
					if (json == null) {
						json = "[]";
					}
					serveJSON(json, response);
				} else {
					String json = result.getResource().get(result.getParsedPath().getId());
					if (json != null) {
						serveJSON(json, response);
					} else {
						sendNotFound(response, "Can not find a resource with id: " + result.getParsedPath().getId());
					}
				}
			} catch (ResourceException e) {
				Logger.getLogger(ResourceDispatcher.class.getName()).log(Level.SEVERE, null, e);
				throw new ServletException(e);
			}
		} else {
			response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
		}
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
			IOException {
		if (isAcceptable(request)) {
			try {
				RequestProcessorResult result = processRequest(request);
				String newId = result.getResource().create();
				if (newId != null) {
					newId = URLEncoder.encode(newId, "UTF-8");
					sendLocation(request, response, result.getParsedPath().getName(), newId);
				} else {
					sendInternalError(response, "Resource " + result.getParsedPath().getName() + " did not return an ID from create");
				}
			} catch (Exception e) {
				Logger.getLogger(ResourceDispatcher.class.getName()).log(Level.SEVERE, null, e);
				throw new ServletException(e);
			}
		} else {
			response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
		}
	}

	public void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException,
			IOException {
		if (isAcceptable(request)) {
			try {
				RequestProcessorResult result = processRequest(request);
				String id = result.getParsedPath().getId();
				result.getResource().delete(id);
				// Could send a 204 and no body also
				response.setContentType("text/plain");
				response.setStatus(HttpServletResponse.SC_OK);
				PrintWriter writer = response.getWriter();
				writer.print("Deleted");
				writer.flush();
			} catch (ResourceException e) {
				Logger.getLogger(ResourceDispatcher.class.getName()).log(Level.SEVERE, null, e);
				throw new ServletException(e);
			}
		} else {
			response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
		}
	}

	public void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		if (isAcceptable(request)) {
			try {
				RequestProcessorResult result = processRequest(request);
				String id = result.getParsedPath().getId();
				result.getResource().update(id);
				// Could send a 204 and no body also
				response.setContentType("text/plain");
				response.setStatus(HttpServletResponse.SC_OK);
				PrintWriter writer = response.getWriter();
				writer.print("Updated");
				writer.flush();
			} catch (ResourceConflictException ex) {
				response.setStatus(HttpServletResponse.SC_CONFLICT);
			} catch (ResourceException ex) {
				Logger.getLogger(ResourceDispatcher.class.getName()).log(Level.SEVERE, null, ex);
				throw new ServletException(ex);
			}
		} else {
			response.setStatus(HttpServletResponse.SC_NOT_ACCEPTABLE);
		}
	}

	@SuppressWarnings("unchecked")
	private boolean isAcceptable(HttpServletRequest request) {
		Enumeration acceptEnum = request.getHeaders("Accept");
		if (acceptEnum.hasMoreElements()) {
			// There is an accept header, so try to find a valid value
			boolean acceptable = false;
			while (acceptEnum.hasMoreElements() && !acceptable) {
				String value = (String) acceptEnum.nextElement();
				if (value.contains("application/json") || value.contains("application/*") || value.contains("*/*")) {
					acceptable = true;
				}
			}
			return acceptable;
		} else {
			return true;
		}
	}

	private RequestProcessorResult processRequest(final HttpServletRequest request) throws MarshallingException,
			ResourceCreationException {
		RequestProcessor requestProcessor = new RequestProcessor(resourceFactory, marshaller);
		return requestProcessor.processRequest(request);
	}

	private void serveJSON(String message, HttpServletResponse response) throws IOException {
		response.setContentType("application/json; charset=UTF-8");
		response.setStatus(HttpServletResponse.SC_OK);
		PrintWriter writer = response.getWriter();
		writer.print(message);
		writer.flush();
	}

	private void sendNotFound(HttpServletResponse response, String content) throws IOException {
		response.setContentType("text/plain");
		response.setStatus(HttpServletResponse.SC_NOT_FOUND);
		print(response, content);
	}

	private void sendInternalError(HttpServletResponse response, String message) throws IOException {
		response.setContentType("text/plain");
		response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
		PrintWriter writer = response.getWriter();
		writer.print(message);
		writer.flush();
	}

	private void print(HttpServletResponse response, String content) throws IOException {
		PrintWriter writer = response.getWriter();
		writer.write(content);
	}

	private void sendLocation(HttpServletRequest request, HttpServletResponse response, String resourceName,
			String newId) throws IOException {
		StringBuffer location = request.getRequestURL();
		location.append("/");
		location.append(newId);

		response.setHeader("Location", location.toString());
		response.setStatus(HttpServletResponse.SC_CREATED);
		response.setContentType("text/html");
		StringBuffer responseBuffer = new StringBuffer();
		responseBuffer.append("<html><body><a href=\"");
		responseBuffer.append(location);
		responseBuffer.append("\">");
		responseBuffer.append(location);
		responseBuffer.append("</a></body></html>");
		print(response, responseBuffer.toString());
	}
}
